package my.framework.web;

import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.invoke.CallSite;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

@MultipartConfig
public class DispatcherServlet extends HttpServlet {
    private static final long serialVersionUID = -3619491573769301637L;

    /**
     * 默认的web名，可以在web.xml中通过初始化参数webPackage修改
     */
    private String webPackage = "my.framework.web";

    /**
     * 配置信息初始化器，加载配置文件并保存
     */
    private ControllerMappingManager ctrlMappingMgr;

    /**
     * 存放Controller实例的HashMap，key为Controller的全类名，value为key所指的类的实例
     */
    protected Map<String, Object> controllers = new HashMap<String,Object>();

    /**
     * 为每个Controller创建一个实例，，key为Controller的全类名，value为methods实例
     */
    protected Map<String, Method> methods = new HashMap<String,Method>();

    public void init() throws ServletException {
        //读取web.xml中的初始化参数
        String webPackage = this.getServletConfig().getInitParameter("webPackage");
        if (!(webPackage == null || (webPackage = webPackage.trim()).equals("")))
            this.webPackage = webPackage;

        //创建 ControllerMappingManager 实例。
        //用于扫描指定包下的类和方法，并构建 URL 到控制器方法的映射关系。
        ctrlMappingMgr = new ControllerMappingManager(this.webPackage);

        //给括号内的对象加了锁，等执行完全部内容后在释放锁
        synchronized (controllers){
            controllers.clear();
        }

        synchronized (methods){
            methods.clear();
        }
    }

    public void destroy(){
        super.destroy();    //just puts "destroy" string in log
        synchronized (controllers){
            controllers.clear();
        }
        synchronized (methods){
            methods.clear();
        }
        this.ctrlMappingMgr.getControllerMappings().clear();
    }

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, java.io.IOException {
        doPost(request, response);
    }

    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, java.io.IOException {
        request.setCharacterEncoding("utf-8");
        response.setCharacterEncoding("utf-8");
        execute(request, response);

    }

    /**
     * 根据映射信息获取Controller的的调用信息，包括类和方法两部分信息，映射信息形式为：/aaa/.../zzz
     */
    private void execute(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        String path = null;
        //先考虑前缀映射，即Servlet采用/xxx/*的映射方式
        //取出Servlet访问路径包含的映射信息
        //取到的是/xxx后面的部分，带/开头
        //先看一下是不是包含动作<jsp:include>发出的请求，如果是，则此项有值
        path = (String) request.getAttribute("javax.servlet.include.path_info");
        if (path == null){  //不是包含动作则正常取值
            path = request.getPathInfo();
            //依然为空说明不是按前缀映射
            if (path == null || (path = path.trim()).isEmpty()){
                //再考虑按扩展名映射，即Servlet采用*.xxx的映射方式，取出访问路径包含的扩展名
                path = (String) request.getAttribute("javax.servlet.include.servlet_path");
                if (path == null){
                    path = request.getServletPath();
                }
                //把扩展名截掉
                int slash = path.lastIndexOf('/');
                int period = path.lastIndexOf('.');
                if (period >= 0 && period > slash){
                    path = path.substring(0, period);
                }
            }
        }
        System.out.println("==================================>path:" + path);

        //如果找不到Controller的信息，抛出PathException异常
        if (!ctrlMappingMgr.containKey(path))
            throw new PathException("No Controller & Method is mapped with this path:" + path);
        ControllerMapping controllerMapping = ctrlMappingMgr.getControllerMapping(path);
        try  {
            Object instance = null;
            //根据得到的全类名，创建该类的唯一实例
            synchronized (controllers){
                //从集合属性controllers中查找该类的实例
                instance = controllers.get(controllerMapping.getClassName());
                if (instance == null){
                    instance = Class.forName(controllerMapping.getClassName()).newInstance();
                    //将该类的实例放入集合属性controllers中
                    controllers.put(controllerMapping.getClassName(), instance);
                }
            }
            //调用指定方法
            Method  method = null;
            synchronized (methods){
                //从集合属性methods中查找该类的方法
                method = methods.get(path);
                if (method == null){
                    method  = instance.getClass().getMethod(controllerMapping.getMethodName(),
                            HttpServletRequest.class, HttpServletResponse.class);
                }
            }
            Object result = method.invoke(instance, request, response);
            toView(result ,request, response);

        }catch (InstantiationException e){
            //无法确定实例化
            throw new RuntimeException(e);
        }catch (IllegalAccessException e){
            //无法正确调用方法
            throw new RuntimeException(e);
        }catch (ClassNotFoundException e){
            //找不到类
            throw new RuntimeException(e);
        }catch (NoSuchMethodException e){
            //找不到方法
            throw new RuntimeException(e);
        }catch (InvocationTargetException e){
            throw new RuntimeException(e);
        }
    }
    protected void toView( Object result ,HttpServletRequest request, HttpServletResponse response)
            throws IOException ,ServletException {
        if (result == null)
            return;
        if (result instanceof String) {
            boolean isRediresct = false;
            String url = (String) result;
            if (url.startsWith("redirect:")){
                isRediresct = true;
                url = url.substring("redirect:".length());
            } else if (url.startsWith("forward:")) {
                url = url.substring("forward:".length());
            }
            if (!(url = url.trim()).startsWith("/"))
                throw new ViewPathException();
            if (isRediresct) {
                response.sendRedirect(request.getContextPath() + url);
            } else {
                request.getRequestDispatcher(url).forward(request, response);
            }
        }else {
            PrintUtil.write(result, response);
        }
    }
}
